/**
 * Copyright 2013 yangming.liu<liuyangming@gmail.com>.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, see <http://www.gnu.org/licenses/>.
 */
package org.bytesoft.openjta.coordinate.internal;

import java.rmi.RemoteException;
import java.util.List;

import javax.transaction.HeuristicCommitException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;

import org.bytesoft.openjta.TransactionStatus;
import org.bytesoft.openjta.archive.TerminatorArchive;
import org.bytesoft.openjta.archive.TransactionArchive;
import org.bytesoft.openjta.coordinate.TransactionCoordinateContext;
import org.bytesoft.openjta.coordinate.TransactionCoordinator;
import org.bytesoft.openjta.internal.RollbackRequiredException;
import org.bytesoft.openjta.internal.TransactionVote;
import org.bytesoft.openjta.resource.Terminator;
import org.bytesoft.openjta.supports.TransactionLogger;

public class RegularTransactionCoordinator implements TransactionCoordinator {

	@Override
	public int xaPrepare(TransactionCoordinateContext context) throws SystemException, RollbackRequiredException {

		TransactionStatus transactionStatus = context.getTransactionStatus();
		if (transactionStatus.isActive()) {
			// ignore
		} else if (transactionStatus.isRollbackOnly()) {
			return TransactionVote.XA_ROLLBACK_REQUIRED;
		} else if (transactionStatus.isCommitting() || transactionStatus.isCommitFail()
				|| transactionStatus.isCommitted()) {
			return TransactionVote.XA_COMMIT_REQUIRED;
		} else if (transactionStatus.isRollingBack() || transactionStatus.isRollbackFail()
				|| transactionStatus.isRolledBack()) {
			// throw new RollbackRequiredException();
			return TransactionVote.XA_ROLLBACK_REQUIRED;
		} else if (transactionStatus.isPrepared()) {
			return TransactionVote.XA_OK;
		} else if (transactionStatus.isPreparing()) {
			// TODO active-transaction | recovered-transaction
		} else if (transactionStatus.isPrepareFail()) {
			return TransactionVote.XA_ROLLBACK_REQUIRED;
		} else {
			throw new RollbackRequiredException();
		}

		List<TerminatorArchive> terminatorArchives = context.getTerminatorArchives();
		Terminator localTerminator = context.getLocalTerminator();

		TransactionLogger transactionLogger = context.getTransactionLogger();

		int[] resourceVotes = new int[terminatorArchives.size() + 1];

		// step1: prepare local terminator
		try {
			TransactionVote vote = localTerminator.xaPrepare();
			resourceVotes[0] = vote.getValue();
		} catch (RemoteException ex) {
			resourceVotes[0] = TransactionVote.XA_ROLLBACK_REQUIRED;
		} catch (SystemException ex) {
			resourceVotes[0] = TransactionVote.XA_ROLLBACK_REQUIRED;
		} catch (RuntimeException ex) {
			resourceVotes[0] = TransactionVote.XA_ROLLBACK_REQUIRED;
		}

		// step2: prepare remote teminators
		for (int i = 0; i < terminatorArchives.size(); i++) {
			TerminatorArchive archive = terminatorArchives.get(i);
			if (archive.isPrepareSuccess()) {
				resourceVotes[i + 1] = archive.getResourceVote();
			} else if (archive.isPrepareFailure()) {
				// never happen
				resourceVotes[i + 1] = archive.getResourceVote();
			} else {
				try {
					Terminator terminator = archive.getTerminator();
					archive.setPrepareStarted(true);
					transactionLogger.enterPrepareTerminator(archive);
					TransactionVote vote = terminator.xaPrepare();
					resourceVotes[i + 1] = vote.getValue();
				} catch (RemoteException e) {
					e.printStackTrace();
					resourceVotes[i + 1] = TransactionVote.XA_ROLLBACK_REQUIRED;
				} catch (SystemException e) {
					e.printStackTrace();
					resourceVotes[i + 1] = TransactionVote.XA_ROLLBACK_REQUIRED;
				} catch (RuntimeException e) {
					e.printStackTrace();
					resourceVotes[i + 1] = TransactionVote.XA_ROLLBACK_REQUIRED;
				} finally {
					archive.setPrepareResult(true);
					archive.setResourceVote(resourceVotes[i + 1]);
					transactionLogger.completePrepareTerminator(archive);
				}// end-finally
			}// end-else
		}

		// step3
		int globalVote = TransactionVote.XA_RDONLY;
		for (int i = 0; i < resourceVotes.length; i++) {
			int vote = resourceVotes[i];
			if (vote == XAResource.XA_RDONLY) {
				// ignore
			} else if (vote == XAResource.XA_OK) {
				globalVote = TransactionVote.XA_OK;
			} else {
				throw new SystemException();
			}
		}
		return globalVote;
	}

	public void xaCommit(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicRollbackException, RollbackException, SecurityException, SystemException {
		List<TerminatorArchive> terminators = context.getTerminatorArchives();
		Terminator localTerminator = context.getLocalTerminator();

		TransactionLogger transactionLogger = context.getTransactionLogger();

		// step1: prepare local terminator
		boolean mixedExists = false;
		boolean failExists = false;
		boolean commitExists = false;
		boolean rollbackExists = false;
		try {
			localTerminator.xaCommit();
			commitExists = true;
		} catch (HeuristicMixedException e) {
			mixedExists = true;
		} catch (HeuristicRollbackException e) {
			rollbackExists = true;
		} catch (RollbackException e) {
			rollbackExists = true;
		} catch (SecurityException e) {
			failExists = true;
		} catch (SystemException e) {
			failExists = true;
		} catch (RemoteException e) {
			failExists = true;
		} catch (RuntimeException e) {
			failExists = true;
		}

		// step2: prepare remote teminators
		for (int i = 0; i < terminators.size(); i++) {
			TerminatorArchive archive = terminators.get(i);
			boolean commitRequired = true;
			if (archive.isRollbackSuccess()) {
				rollbackExists = true;
				commitRequired = false;
			} else if (archive.isCommitSuccess()) {
				commitExists = true;
				commitRequired = false;
			} else if (archive.isCommitFailure()) {
				if (archive.isHeuristicExists()) {
					int errcode = archive.getHeuristicCode();
					if (errcode == XAException.XA_HEURCOM) {
						commitExists = true;
						commitRequired = false;
					} else if (errcode == XAException.XA_HEURRB) {
						rollbackExists = true;
						commitRequired = false;
					} else {
						// ignore
					}
				}
			} else if (archive.isPrepareCompleted() && archive.getResourceVote() == TransactionVote.XA_RDONLY) {
				commitRequired = false;
			}

			if (commitRequired) {
				boolean success = false;
				try {
					Terminator terminator = archive.getTerminator();
					archive.setCommitStarted(true);
					transactionLogger.enterCommitTerminator(archive);
					terminator.xaCommit();
					success = true;
					commitExists = true;

					/* update the heuristic status */
					archive.setHeuristicExists(false);
					archive.setHeuristicCode(0);
				} catch (HeuristicMixedException ex) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURMIX);
					mixedExists = true;
				} catch (HeuristicRollbackException ex) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURRB);
					rollbackExists = true;
				} catch (RollbackException ex) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURRB);
					rollbackExists = true;
				} catch (SecurityException ex) {
					failExists = true;
				} catch (SystemException ex) {
					failExists = true;
				} catch (RemoteException ex) {
					failExists = true;
				} catch (RuntimeException ex) {
					failExists = true;
				} finally {
					archive.setCommitResult(success);
					transactionLogger.completeCommitTerminator(archive);
				}
			}
		}

		if (commitExists && rollbackExists) {
			throw new HeuristicMixedException();
		} else if (mixedExists) {
			throw new HeuristicMixedException();
		} else if (failExists) {
			throw new SystemException();
		} else {
			if (rollbackExists) {
				throw new HeuristicRollbackException();
			}
		}
	}

	public void processCommit(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicRollbackException, RollbackException, SecurityException, SystemException {
		TransactionStatus transactionStatus = context.getTransactionStatus();
		TransactionLogger transactionLogger = context.getTransactionLogger();
		TransactionArchive transactionArchive = context.getTransactionArchive();

		// step1: prepare
		boolean rdonly = false;
		boolean rollbackRequired = false;
		int vote = TransactionVote.XA_ROLLBACK_REQUIRED;
		try {
			vote = this.xaPrepare(context);
			if (vote == TransactionVote.XA_RDONLY) {
				rdonly = true;
			}
		} catch (RollbackRequiredException ex) {
			rollbackRequired = true;
		} catch (SystemException ex) {
			rollbackRequired = true;
		} catch (RuntimeException ex) {
			rollbackRequired = true;
		}

		transactionStatus.transferToPrepared();

		transactionLogger.completePreparePhase(transactionArchive);

		// step2: commit/rollback
		if (rdonly) {
			transactionLogger.completeCommitPhase(transactionArchive);
		} else if (rollbackRequired) {
			try {
				this.xaRollback(context);
				throw new HeuristicRollbackException();
			} catch (HeuristicCommitException e) {
				// ignore
			}
		} else {
			boolean success = false;
			transactionStatus.transferToCommitting();
			transactionLogger.enterCommitPhase(transactionArchive);
			try {
				this.xaCommit(context);
				success = true;
			} finally {
				if (success) {
					transactionStatus.transferToCommitted();
				} else {
					transactionStatus.transferToCommitFail();
				}
				transactionLogger.completeCommitPhase(transactionArchive);
			}// end-finally
		}
	}

	public void processRollback(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicCommitException, SystemException {
		TransactionStatus transactionStatus = context.getTransactionStatus();
		TransactionLogger transactionLogger = context.getTransactionLogger();
		TransactionArchive transactionArchive = context.getTransactionArchive();

		boolean success = false;
		transactionStatus.transferToRollingback();
		transactionLogger.enterRollbackPhase(transactionArchive);
		try {
			this.xaRollback(context);
			success = true;
		} finally {
			if (success) {
				transactionStatus.transferToRolledback();
			} else {
				transactionStatus.transferToRollbackFail();
			}
			transactionLogger.completeRollbackPhase(transactionArchive);
		}
	}

	public void xaRollback(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicCommitException, SystemException {
		List<TerminatorArchive> terminatorArchives = context.getTerminatorArchives();
		Terminator localTerminator = context.getLocalTerminator();

		TransactionLogger transactionLogger = context.getTransactionLogger();

		// step1: prepare local terminator
		boolean mixedExists = false;
		boolean failExists = false;
		boolean commitExists = false;
		boolean rollbackExists = false;
		try {
			localTerminator.xaRollback();
			rollbackExists = true;
		} catch (HeuristicMixedException e) {
			mixedExists = true;
		} catch (HeuristicCommitException e) {
			commitExists = true;
		} catch (SystemException e) {
			failExists = true;
		} catch (RemoteException e) {
			failExists = true;
		} catch (RuntimeException e) {
			failExists = true;
		}

		// step2: prepare remote teminators
		for (int i = 0; i < terminatorArchives.size(); i++) {
			TerminatorArchive archive = terminatorArchives.get(i);

			boolean rollbackRequired = true;
			if (archive.isCommitSuccess()) {
				commitExists = true;
				rollbackRequired = false;
			} else if (archive.isRollbackSuccess()) {
				rollbackExists = true;
				rollbackRequired = false;
			} else if (archive.isRollbackFailure()) {
				if (archive.isHeuristicExists()) {
					int errcode = archive.getHeuristicCode();
					if (errcode == XAException.XA_HEURCOM) {
						commitExists = true;
						rollbackRequired = false;
					} else if (errcode == XAException.XA_HEURRB) {
						rollbackExists = true;
						rollbackRequired = false;
					} else {
						// ignore
					}
				}
			} else if (archive.isPrepareCompleted() && archive.getResourceVote() == TransactionVote.XA_RDONLY) {
				rollbackRequired = false;
			}

			if (rollbackRequired) {
				boolean success = false;
				try {
					Terminator terminator = archive.getTerminator();
					archive.setRollbackStarted(true);
					transactionLogger.enterRollbackTerminator(archive);
					terminator.xaRollback();
					success = true;
					rollbackExists = true;

					/* update the heuristic status */
					archive.setHeuristicExists(false);
					archive.setHeuristicCode(0);
				} catch (HeuristicMixedException e) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURMIX);
					mixedExists = true;
				} catch (HeuristicCommitException e) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURCOM);
					commitExists = true;
				} catch (SystemException e) {
					failExists = true;
				} catch (RemoteException e) {
					failExists = true;
				} catch (RuntimeException e) {
					failExists = true;
				} finally {
					archive.setRollbackResult(success);
					transactionLogger.completeRollbackTerminator(archive);
				}
			}
		}

		if (commitExists && rollbackExists) {
			throw new HeuristicMixedException();
		} else if (mixedExists) {
			throw new HeuristicMixedException();
		} else if (failExists) {
			throw new SystemException();
		} else if (commitExists) {
			throw new SystemException();
		}

	}

	public void recoveryXACommit(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicRollbackException, RollbackException, SecurityException, SystemException {
		this.xaCommit(context);
	}

	public void recoveryXARollback(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicCommitException, SystemException {
		this.xaRollback(context);
	}

	public void processRecoveryCommit(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicRollbackException, RollbackException, SecurityException, SystemException {
		// TODO
		this.processCommit(context);
	}

	public void processRecoveryRollback(TransactionCoordinateContext context) throws HeuristicMixedException,
			HeuristicCommitException, SystemException {
		// TODO
		this.processRollback(context);
	}

}
